home

load libraries


### https://cran.r-project.org/web/packages/udpipe/vignettes/udpipe-usecase-postagging-lemmatisation.html
library(udpipe)
ud_model <- udpipe_download_model(language = "english")
library(tidyverse)
library(tidyr)
library(dplyr)
library(ggplot2)
library(ggrepel)
library(knitr)
library(tm)
library(quanteda)
library(lattice)
library(latticeExtra)
library(plotly)
library(pdp)
library(patchwork)

load the FW functions


### CODE DIRECTLY FROM: https://burtmonroe.github.io/TextAsDataCourse/Tutorials/TADA-FightinWords.nb.html#

fwgroups <- function(dtm, groups, pair = NULL, weights = rep(1,nrow(dtm)), k.prior = .1) {
  
  weights[is.na(weights)] <- 0
  
  weights <- weights/mean(weights)
  
  zero.doc <- rowSums(dtm)==0 | weights==0
  zero.term <- colSums(dtm[!zero.doc,])==0
  
  dtm.nz <- apply(dtm[!zero.doc,!zero.term],2,"*", weights[!zero.doc])
  
  g.prior <- tcrossprod(rowSums(dtm.nz),colSums(dtm.nz))/sum(dtm.nz)
  
  # 
  
  g.posterior <- as.matrix(dtm.nz + k.prior*g.prior)
  
  groups <- groups[!zero.doc]
  groups <- droplevels(groups)
  
  g.adtm <- as.matrix(aggregate(x=g.posterior,by=list(groups=groups),FUN=sum)[,-1])
  rownames(g.adtm) <- levels(groups)
  
  g.ladtm <- log(g.adtm)
  
  g.delta <- t(scale( t(scale(g.ladtm, center=T, scale=F)), center=T, scale=F))
  
  g.adtm_w <- -sweep(g.adtm,1,rowSums(g.adtm)) # terms not w spoken by k
  g.adtm_k <- -sweep(g.adtm,2,colSums(g.adtm)) # w spoken by groups other than k
  g.adtm_kw <- sum(g.adtm) - g.adtm_w - g.adtm_k - g.adtm # total terms not w or k 
  
  g.se <- sqrt(1/g.adtm + 1/g.adtm_w + 1/g.adtm_k + 1/g.adtm_kw)
  
  g.zeta <- g.delta/g.se
  
  g.counts <- as.matrix(aggregate(x=dtm.nz, by = list(groups=groups), FUN=sum)[,-1])
  
  if (!is.null(pair)) {
    pr.delta <- t(scale( t(scale(g.ladtm[pair,], center = T, scale =F)), center=T, scale=F))
    pr.adtm_w <- -sweep(g.adtm[pair,],1,rowSums(g.adtm[pair,]))
    pr.adtm_k <- -sweep(g.adtm[pair,],2,colSums(g.adtm[pair,])) # w spoken by groups other than k
    pr.adtm_kw <- sum(g.adtm[pair,]) - pr.adtm_w - pr.adtm_k - g.adtm[pair,] # total terms not w or k
    pr.se <- sqrt(1/g.adtm[pair,] + 1/pr.adtm_w + 1/pr.adtm_k + 1/pr.adtm_kw)
    pr.zeta <- pr.delta/pr.se
    
    return(list(zeta=pr.zeta[1,], delta=pr.delta[1,],se=pr.se[1,], counts = colSums(dtm.nz), acounts = colSums(g.adtm)))
  } else {
    return(list(zeta=g.zeta,delta=g.delta,se=g.se,counts=g.counts,acounts=g.adtm))
  }
}

############## FIGHTIN' WORDS PLOTTING FUNCTION

# helper function
makeTransparent<-function(someColor, alpha=100)
{
  newColor<-col2rgb(someColor)
  apply(newColor, 2, function(curcoldata){rgb(red=curcoldata[1], green=curcoldata[2],
                                              blue=curcoldata[3],alpha=alpha, maxColorValue=255)})
}

fw.ggplot.groups <- function(fw.ch, groups.use = as.factor(rownames(fw.ch$zeta)), max.words = 50, max.countrank = 400, colorpalette=rep("black",length(groups.use)), sizescale=2, title="Comparison of Terms by Groups", subtitle = "", caption = "Group-specific terms are ordered by Fightin' Words statistic (Monroe, et al. 2008)") {
  if (is.null(dim(fw.ch$zeta))) {## two-group fw object consists of vectors, not matrices
    zetarankmat <- cbind(rank(-fw.ch$zeta),rank(fw.ch$zeta))
    colnames(zetarankmat) <- groups.use
    countrank <- rank(-(fw.ch$counts))
  } else {
    zetarankmat <- apply(-fw.ch$zeta[groups.use,],1,rank)
    countrank <- rank(-colSums(fw.ch$counts))
  }
  wideplotmat <- as_tibble(cbind(zetarankmat,countrank=countrank))
  wideplotmat$term=names(countrank)
  #rankplot <- gather(wideplotmat, party, zetarank, 1:ncol(zetarankmat))
  rankplot <- gather(wideplotmat, groups.use, zetarank, 1:ncol(zetarankmat))
  rankplot$plotsize <- sizescale*(50/(rankplot$zetarank))^(1/4)
  rankplot <- rankplot[rankplot$zetarank < max.words + 1 & rankplot$countrank<max.countrank+1,]
  rankplot$groups.use <- factor(rankplot$groups.use,levels=groups.use)
  
  p <- ggplot(rankplot, aes((nrow(rankplot)-countrank)^1, -(zetarank^1), colour=groups.use)) + 
    geom_point(show.legend=F,size=sizescale/2) + 
    theme_classic() +
    theme(axis.ticks=element_blank(), axis.text=element_blank() ) +
    ylim(-max.words,40) +
    facet_grid(groups.use ~ .) +
    geom_text_repel(aes(label = term), size = rankplot$plotsize, point.padding=.05,
                    box.padding = unit(0.20, "lines"), show.legend=F, max.overlaps = Inf) +
    scale_colour_manual(values = alpha(colorpalette, .7)) + 
#    labs(x="Terms used more frequently overall →", y="Terms used more frequently by group →",  title=title, subtitle=subtitle , caption = caption) 
    labs(x=paste("Terms used more frequently overall -->"), y=paste("Terms used more frequently by group -->"),  title=title, subtitle=subtitle , caption = caption) 
  
}

options(ggrepel.max.overlaps = Inf)

fw.keys <- function(fw.ch,n.keys=10) {
  n.groups <- nrow(fw.ch$zeta)
  keys <- matrix("",n.keys,n.groups)
  colnames(keys) <- rownames(fw.ch$zeta)
  
  for (g in 1:n.groups) {
    keys[,g] <- names(sort(fw.ch$zeta[g,],dec=T)[1:n.keys])
  }
  keys
}

Compare Associated Press 1994-2010: Before and After “extremist” and other query terms

Query search: (activi* | ahbash | akromiya | anjem | ansharut | anticapital* | antidemocr* | antiestablish* | antifa | antigovern* | antimilitar* | antimonarch* | antipatri* | antireli* | antisem* | antisocia* | antisyst* | apost* | atharis | athei* | atheists | außerparlamentari* | authoritar* | bagau* | bigots | bplf | bukhari* | capitulatio* | conspirato* | counterj* | cybercalip* | damigo | dawro* | demon* | deradicaliza* | deviatio* | diqqi | dissid* | djamaat | dotbus* | ecofas* | espou* | ethnonationa* | extrem* | facists | fadaia* | fanat* | fasci* | fetö | fightdem* | freedo* | fundamental* | fuqra | gafatar | gamerga* | gemidzii | ghuluww | globali* | gramsc* | gülen* | hacktiv* | haquna | hardline | harkatul | hatemon* | heimwe* | hezbol* | hinduph* | hindutva | hizbut | hojja* | ideolog* | incitem* | inciters | insurr* | intacti* | islam4uk | islam* | jaljalat | jbakc | jihadi* | jmjb | jrtn | judai* | juhayman | jundu* | kadiza* | kahanism | kahanist | karram* | kaysa* | khalis* | khatmia | khawarij | khomein* | koutla | leftist | leftists | leftwing | liberatio* | madkha* | madkhal* | maimonid* | manosp* | mauras* | mcln | militan* | millatu | monarc* | mudja* | muhaji* | mujahid* | murab* | muttahi* | najjadah | nationali* | neofas* | neona* | opantish | oppositi* | paleolibertar* | paramili* | parliamenta* | pegida | populist | principa* | profe* | prowar | putinist | qadari | quranism | quranist | qutbism | qutbist | qutbists | racis* | radica* | reactioni* | reformis* | reichsbürgerbewe* | rightist | rightw* | rofiq | russoph* | sabireen | sadda* | salaf* | sayaff | scriptura* | secula* | separationi* | sharia4hol* | sikrikim | split* | squadism | strasse* | subver* | suidlan* | sukarn* | suprema* | supremac* | sympathi* | table* | tabliq | takfir | takfir* | takfi* |terror* | theoc* | titoite | triba* | trots* | trotsk* | ukrainoph* | ultraconserva* | ultralib* | ultranationa* | ultrar* | uscmo | wahabbi | wahab* | wahha* | xenoph* | yulde* | zinovie*)

Load and clean the data

text_cleaner<-function(corpus){
  tempcorpus<-Corpus(VectorSource(corpus))
  tempcorpus<-tm_map(tempcorpus,
                    removePunctuation)
  tempcorpus<-tm_map(tempcorpus,
                    stripWhitespace)
  tempcorpus<-tm_map(tempcorpus,
                    removeNumbers)
  tempcorpus<-tm_map(tempcorpus,
                     removeWords, stopwords("english"))
  tempcorpus<-tm_map(tempcorpus, 
                    stemDocument)
  return(tempcorpus)
}

Calculate FW.


e <- dfm(extremecorpus$content)
message(dim(e))
head(e)
Document-feature matrix of: 6 documents, 15,213 features (100.0% sparse).
       features
docs    us struggl uptick american plot attack past month last week
  text1  1       1      1        1    1      0    0     0    0    0
  text2  0       0      0        0    0      1    1     1    1    1
  text3  0       0      0        0    0      0    0     0    0    0
  text4  0       0      0        0    0      0    0     0    0    0
  text5  0       0      0        0    0      0    0     0    0    0
  text6  0       0      0        0    0      0    0     0    0    0
[ reached max_nfeat ... 15,203 more features ]
#############################################

e <- dfm_select(e, pattern = stopwords("english"), selection = "remove")
e <- dfm_select(e, min_nchar = 2)
e <- dfm_trim(e, min_termfreq = 4, min_docfreq = .05, verbose=TRUE)

#dim(e)
# sparsity(e)

#############################################

extrem_dtm <- convert(e, to='data.frame')
extrem_dtm <- extrem_dtm[-c(1)]
w <- which( sapply(extrem_dtm, class ) == 'character' )

#############################################

fw.extrem <- fwgroups(extrem_dtm, groups=extrem_NYT.dfm.long$Context)

rm(extrem_dtm)

Get and show the top words per group by zeta.


fwkeys.extrem <- fw.keys(fw.extrem, n.keys=20)
cols <- rev(colnames(fwkeys.extrem))
fwkeys.extrem <- fwkeys.extrem[,cols]
kable(fwkeys.extrem)
Context.before Context.after
islamic group
palestinian hussein
war attack
iraqi parti
suspect elect
crack leader
jemaah jihad
crackdown milit
main movement
alleg said
support guerrilla
evid network
albanian regim
prodemocraci armi
sept organ
megawati politician
muslim outsid
prevent univers
kosovo bomb
battl rocket
NA

Plot: Before in Blue, After in Red


p.fw.extrem <- fw.ggplot.groups(fw.extrem,sizescale=4,max.words=200,
                                max.countrank=400,colorpalette=c("red","blue"),
                                title = 'Comparison of Terms Before and After Query Word')
p.fw.extrem

Calculate by query item/search term


extrem_NYT.dfm.long$Query.item <- as.factor(extrem_NYT.dfm.long$Query.item)

top_n <-as.data.frame(sort(table(extrem_NYT.dfm.long$Query.item), decreasing = TRUE)[1:5]) 
message(dim(top_n))
colnames(top_n) <- c('term', 'Freq')
message(top_n)

extrem_dtm_topn <- convert(e, to='data.frame')
extrem_dtm_topn$Number.of.hit <- extrem_NYT.dfm.long$Number.of.hit

topn_terms <- extrem_NYT.dfm.long %>%
      filter(Query.item %in% top_n$term)

extrem_dtm_topn_keep <- extrem_dtm_topn %>% 
    filter(Number.of.hit %in% topn_terms$Number.of.hit)

r <- sum(length(extrem_dtm_topn_keep))

extrem_dtm_topn_keep <- extrem_dtm_topn_keep[-c(1, r)]

fw.query_item <- fwgroups(extrem_dtm_topn_keep,groups = topn_terms$Query.item)

extrem_dtm_topn <- convert(e, to='data.frame')
extrem_dtm_topn$Number.of.hit <- extrem_NYT.dfm.long$Number.of.hit
extrem_dtm_topn$Context <- extrem_NYT.dfm.long$Context

topn_terms <- extrem_NYT.dfm.long %>%
      filter(Query.item %in% top_n$term)

extrem_dtm_topn_keep <- extrem_dtm_topn %>% 
    filter(Number.of.hit %in% topn_terms$Number.of.hit)

extrem_dtm_topn_keep_before <- extrem_dtm_topn_keep[grep("before",extrem_dtm_topn_keep$Context),]
extrem_dtm_topn_keep_after <- extrem_dtm_topn_keep[grep("after", extrem_dtm_topn_keep$Context),]

rr <-dim(topn_terms)[1]
r <- sum(length(extrem_dtm_topn_keep_before))
topn_terms_before <- topn_terms[seq(1,rr,2),]
topn_terms_after <- topn_terms[seq(2,rr,2),]

extrem_dtm_topn_keep_before <- extrem_dtm_topn_keep_before[-c(1, r-1, r)]
extrem_dtm_topn_keep_after <- extrem_dtm_topn_keep_after[-c(1, r-1, r)]

rm(rr)
rm(r)
rm(extrem_dtm_topn)

#############################################
fw.query_item_before <- fwgroups(extrem_dtm_topn_keep_before,groups = topn_terms_before$Query.item)
fwkeys.query_item_before <- fw.keys(fw.query_item_before, n.keys=15)
kable(fwkeys.query_item_before, caption = "Top 15 Words for Query Term: BEFORE")
Top 15 Words for Query Term: BEFORE
Islamic militants opposition Saddam terrorist
milit islam main iraqi sept
organ palestinian elect presid alqaida
strict kill polit iraq bin
radic suspect parliament war laden
somalia attack opposit saddam involv
hama taliban parti oust unit
secular isra minist baghdad suspect
hardlin muslim vote fall link
iran gaza democrat bush state
turkey armi prime hussein consid
extremist troop strong captur connect
malaysia israel percent regim appar
suprem pakistan despit usl osama
gaza forc poll former charg
fundamentalist arafat voic death list

p.fw.query_item_before <- fw.ggplot.groups(fw.query_item_before,sizescale=2,max.words=150,max.countrank=400,
                                           colorpalette = c('blue','blue','blue', 'blue','blue'),
                                           title = 'Comparison of Terms by Overall Top Terms: BEFORE')
p.fw.query_item_before


#############################################
fw.query_item_after <- fwgroups(extrem_dtm_topn_keep_after,groups = topn_terms_after$Query.item)
fwkeys.query_item_after <- fw.keys(fw.query_item_after, n.keys=15)
kable(fwkeys.query_item_after, caption = "Top 15 Words for Query Term: AFTER")
Top 15 Words for Query Term: AFTER
Islamic militants opposition Saddam terrorist
milit isra parti hussein attack
group israel leader regim organ
jihad kashmir democrat iraq state
hama gaza vote iraqi unit
movement palestinian parliament un activ
law kill lawmak weapon group
insurg attack protest saddam act
extremist region elect son network
front southern candid captur bomb
revolut fire politician kuwait suspect
republ rocket coalit mass threat
court pakistani social baghdad financ
fundamentalist fight conserv us sept
news border win resolut link
agenc area opposit palac cell

p.fw.query_item_after <- fw.ggplot.groups(fw.query_item_after,sizescale=2,max.words=150,max.countrank=400,
                                           colorpalette = c('red', 'red','red','red','red'),
                                          title = 'Comparison of Terms by Overall Top Terms: AFTER')
p.fw.query_item_after

NA
NA

Calculate Parts of speech by before and after

Calculate FW and keys


ud_model <- udpipe_load_model(ud_model$file_model)

txt <-as.character(extrem_NYT.dfm.long$context.text)

x_udp <- udpipe_annotate(ud_model, x = txt, doc_id = seq_along(txt))
x <- as.data.frame(x_udp)

x$doc_id <-as.integer(x$doc_id)

x_odd.before <- x[x$doc_id %% 2 == 1,]
x_even.after <-x[x$doc_id %% 2 == 0, ]

A few barchart functions


## UNIVERSAL PoS
UPOS_barchart <- function(df1, df2){
  stats1 <- txt_freq(df1$upos)
  stats1$key <- factor(stats1$key, levels = rev(stats1$key))
  
  stats2 <- txt_freq(df2$upos)
  stats2$key <- factor(stats2$key, levels = rev(stats2$key))
  
  c(barchart(key ~ freq, data = stats1, col = "cadetblue", 
        main = "UPOS (Universal Parts of Speech)\n frequency of occurrence: BEFORE vs AFTER", 
         xlab = "Freq"), 
    barchart(key ~ freq, data = stats2, col =  'skyblue',
         xlab = "Freq"))
}



## NOUNS
NOUNS_barchart <- function(df1, df2){
  
  stats1 <- subset(df1, upos %in% c("NOUN")) 
  stats1 <- txt_freq(stats1$token)
  stats1$key <- factor(stats1$key, levels = rev(stats1$key))
  
  stats2 <- subset(df2, upos %in% c("NOUN")) 
  stats2 <- txt_freq(stats2$token)
  stats2$key <- factor(stats2$key, levels = rev(stats2$key))
  
  c(barchart(key ~ freq, data = head(stats1, 20), col = "cadetblue", 
           main = "Most occurring nouns: BEFORE vs AFTER", xlab = "Freq"),
      barchart(key ~ freq, data = head(stats2, 20), col = "skyblue", 
            xlab = "Freq"))
}

## ADJECTIVES
ADJ_barchart <- function(df1, df2){
  
  stats1 <- subset(df1, upos %in% c("ADJ")) 
  stats1 <- txt_freq(stats1$token)
  stats1$key <- factor(stats1$key, levels = rev(stats1$key))
  
  stats2 <- subset(df2, upos %in% c("ADJ")) 
  stats2 <- txt_freq(stats2$token)
  stats2$key <- factor(stats2$key, levels = rev(stats2$key))
  
  c(barchart(key ~ freq, data = head(stats1, 20), col = "cadetblue", 
           main = "Most occurring adjectives: BEFORE vs AFTER", xlab = "Freq"),
      barchart(key ~ freq, data = head(stats2, 20), col = "skyblue", 
         xlab = "Freq"))
}

## Using RAKE to find keywords
RAKE_KW_barchart <- function(df1,df2){
  
  stats1 <- keywords_rake(x = df1, term = "lemma", group = "doc_id", 
                         relevant = df1$upos %in% c("NOUN", "ADJ"))
  stats1$key <- factor(stats1$keyword, levels = rev(stats1$keyword))
  
  stats2 <- keywords_rake(x = df2, term = "lemma", group = "doc_id", 
                         relevant = df2$upos %in% c("NOUN", "ADJ"))
  stats2$key <- factor(stats2$keyword, levels = rev(stats2$keyword))
  
  
  c(barchart(key ~ rake, data = head(subset(stats1, freq > 3), 20), col = "cadetblue", 
           main = "Keywords identified by RAKE: BEFORE vs AFTER", 
           xlab = "Rake"),
    barchart(key ~ rake, data = head(subset(stats2, freq > 3), 20), col = "skyblue", 
           xlab = "Rake"))
}

## Using Pointwise Mutual Information Collocations
PWI_barchart <- function(df1, df2){
  
  df1$word <- tolower(df1$token)
  stats1 <- keywords_collocation(x = df1, term = "word", group = "doc_id")
  stats1$key <- factor(stats1$keyword, levels = rev(stats1$keyword))
  
  df2$word <- tolower(df2$token)
  stats2 <- keywords_collocation(x = df2, term = "word", group = "doc_id")
  stats2$key <- factor(stats2$keyword, levels = rev(stats2$keyword))
  
  c(barchart(key ~ pmi, data = head(subset(stats1, freq > 3), 20), col = "cadetblue", 
           main = "Keywords identified by PMI Collocation: BEFORE vs AFTER", 
           xlab = "PMI (Pointwise Mutual Information)"),
      barchart(key ~ pmi, data = head(subset(stats2, freq > 3), 20), col = "skyblue", 
           xlab = "PMI (Pointwise Mutual Information)"))
}

## Using a sequence of POS tags (noun phrases / verb phrases)
POS_barchart <- function(df1, df2){
  
  df1$phrase_tag <- as_phrasemachine(df1$upos, type = "upos")
  stats1 <- keywords_phrases(x = df1$phrase_tag, term = tolower(df1$token), 
                            pattern = "(A|N)*N(P+D*(A|N)*N)*", 
                            is_regex = TRUE, detailed = FALSE)
  stats1 <- subset(stats1, ngram > 1 & freq > 3)
  stats1$key <- factor(stats1$keyword, levels = rev(stats1$keyword))
  
  df2$phrase_tag <- as_phrasemachine(df2$upos, type = "upos")
  stats2 <- keywords_phrases(x = df2$phrase_tag, term = tolower(df2$token), 
                            pattern = "(A|N)*N(P+D*(A|N)*N)*", 
                            is_regex = TRUE, detailed = FALSE)
  stats2 <- subset(stats2, ngram > 1 & freq > 3)
  stats2$key <- factor(stats2$keyword, levels = rev(stats2$keyword))
  
  c(barchart(key ~ freq, data = head(stats1, 20), col = "cadetblue", 
           main = "Keywords - simple noun phrases: BEFORE vs AFTER", xlab = "Frequency"),
      barchart(key ~ freq, data = head(stats2, 20), col = "skyblue", 
               xlab = "Frequency"))
}

Bar Charts from Functions Above


UPOS_barchart(x_odd.before, x_even.after)

NOUNS_barchart(x_odd.before, x_even.after)

ADJ_barchart(x_odd.before, x_even.after)

RAKE_KW_barchart(x_odd.before, x_even.after)

PWI_barchart(x_odd.before, x_even.after)

POS_barchart(x_odd.before, x_even.after)

Cooccurences


Attaching package: ‘igraph’

The following object is masked from ‘package:plotly’:

    groups

The following object is masked from ‘package:quanteda’:

    as.igraph

The following objects are masked from ‘package:dplyr’:

    as_data_frame, groups, union

The following objects are masked from ‘package:purrr’:

    compose, simplify

The following object is masked from ‘package:tidyr’:

    crossing

The following object is masked from ‘package:tibble’:

    as_data_frame

The following objects are masked from ‘package:stats’:

    decompose, spectrum

The following object is masked from ‘package:base’:

    union

Cooccurences (part 2)


Corrs <- function(df){
  df$id <- unique_identifier(df, fields = c("sentence_id", "doc_id"))
  dtm <- subset(df, upos %in% c("NOUN", "ADJ"))
  dtm <- document_term_frequencies(dtm, document = "id", term = "lemma")
  dtm <- document_term_matrix(dtm)
  dtm <- dtm_remove_lowfreq(dtm, minfreq = 5)
  termcorrelations <- dtm_cor(dtm)
  y <- as_cooccurrence(termcorrelations)
  y <- subset(y, term1 < term2 & abs(cooc) > 0.2)
  y <- y[order(abs(y$cooc), decreasing = TRUE), ]
  print(y[1:25,])
}


Corrs(x_odd.before)
Corrs(x_even.after)

rm(list=ls())

home

LS0tCnRpdGxlOiAiRXh0cmVtKGlzdCArKSBGaWdodGluJyBXb3JkcyIKYXV0aG9yOiAiQnJlYW5uYSBFLiBHcmVlbiIKc3VidGl0bGU6Cm91dHB1dDoKICBodG1sX2RvY3VtZW50OgogICAgdG9jOiB5ZXMKICAgIGRmX3ByaW50OiBwYWdlZAogIGh0bWxfbm90ZWJvb2s6CiAgICBjb2RlX2ZvbGRpbmc6IHNob3cKICAgIGRmX3ByaW50OiBwYWdlZAogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKLS0tCgpbaG9tZV0oaHR0cHM6Ly9icmVncmVlbi5naXRodWIuaW8vKQoKIyMgbG9hZCBsaWJyYXJpZXMKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KCiMjIyBodHRwczovL2NyYW4uci1wcm9qZWN0Lm9yZy93ZWIvcGFja2FnZXMvdWRwaXBlL3ZpZ25ldHRlcy91ZHBpcGUtdXNlY2FzZS1wb3N0YWdnaW5nLWxlbW1hdGlzYXRpb24uaHRtbApsaWJyYXJ5KHVkcGlwZSkKdWRfbW9kZWwgPC0gdWRwaXBlX2Rvd25sb2FkX21vZGVsKGxhbmd1YWdlID0gImVuZ2xpc2giKQoKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3JlcGVsKQpsaWJyYXJ5KGtuaXRyKQpsaWJyYXJ5KHRtKQpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KGxhdHRpY2UpCmxpYnJhcnkobGF0dGljZUV4dHJhKQpsaWJyYXJ5KHBsb3RseSkKbGlicmFyeShwZHApCmxpYnJhcnkocGF0Y2h3b3JrKQoKYGBgCgojIyBsb2FkIHRoZSBGVyBmdW5jdGlvbnMKCmBgYHtyIGxvYWRfZndfZnVuY3Rpb25zfQoKIyMjIENPREUgRElSRUNUTFkgRlJPTTogaHR0cHM6Ly9idXJ0bW9ucm9lLmdpdGh1Yi5pby9UZXh0QXNEYXRhQ291cnNlL1R1dG9yaWFscy9UQURBLUZpZ2h0aW5Xb3Jkcy5uYi5odG1sIwoKZndncm91cHMgPC0gZnVuY3Rpb24oZHRtLCBncm91cHMsIHBhaXIgPSBOVUxMLCB3ZWlnaHRzID0gcmVwKDEsbnJvdyhkdG0pKSwgay5wcmlvciA9IC4xKSB7CiAgCiAgd2VpZ2h0c1tpcy5uYSh3ZWlnaHRzKV0gPC0gMAogIAogIHdlaWdodHMgPC0gd2VpZ2h0cy9tZWFuKHdlaWdodHMpCiAgCiAgemVyby5kb2MgPC0gcm93U3VtcyhkdG0pPT0wIHwgd2VpZ2h0cz09MAogIHplcm8udGVybSA8LSBjb2xTdW1zKGR0bVshemVyby5kb2MsXSk9PTAKICAKICBkdG0ubnogPC0gYXBwbHkoZHRtWyF6ZXJvLmRvYywhemVyby50ZXJtXSwyLCIqIiwgd2VpZ2h0c1shemVyby5kb2NdKQogIAogIGcucHJpb3IgPC0gdGNyb3NzcHJvZChyb3dTdW1zKGR0bS5ueiksY29sU3VtcyhkdG0ubnopKS9zdW0oZHRtLm56KQogIAogICMgCiAgCiAgZy5wb3N0ZXJpb3IgPC0gYXMubWF0cml4KGR0bS5ueiArIGsucHJpb3IqZy5wcmlvcikKICAKICBncm91cHMgPC0gZ3JvdXBzWyF6ZXJvLmRvY10KICBncm91cHMgPC0gZHJvcGxldmVscyhncm91cHMpCiAgCiAgZy5hZHRtIDwtIGFzLm1hdHJpeChhZ2dyZWdhdGUoeD1nLnBvc3RlcmlvcixieT1saXN0KGdyb3Vwcz1ncm91cHMpLEZVTj1zdW0pWywtMV0pCiAgcm93bmFtZXMoZy5hZHRtKSA8LSBsZXZlbHMoZ3JvdXBzKQogIAogIGcubGFkdG0gPC0gbG9nKGcuYWR0bSkKICAKICBnLmRlbHRhIDwtIHQoc2NhbGUoIHQoc2NhbGUoZy5sYWR0bSwgY2VudGVyPVQsIHNjYWxlPUYpKSwgY2VudGVyPVQsIHNjYWxlPUYpKQogIAogIGcuYWR0bV93IDwtIC1zd2VlcChnLmFkdG0sMSxyb3dTdW1zKGcuYWR0bSkpICMgdGVybXMgbm90IHcgc3Bva2VuIGJ5IGsKICBnLmFkdG1fayA8LSAtc3dlZXAoZy5hZHRtLDIsY29sU3VtcyhnLmFkdG0pKSAjIHcgc3Bva2VuIGJ5IGdyb3VwcyBvdGhlciB0aGFuIGsKICBnLmFkdG1fa3cgPC0gc3VtKGcuYWR0bSkgLSBnLmFkdG1fdyAtIGcuYWR0bV9rIC0gZy5hZHRtICMgdG90YWwgdGVybXMgbm90IHcgb3IgayAKICAKICBnLnNlIDwtIHNxcnQoMS9nLmFkdG0gKyAxL2cuYWR0bV93ICsgMS9nLmFkdG1fayArIDEvZy5hZHRtX2t3KQogIAogIGcuemV0YSA8LSBnLmRlbHRhL2cuc2UKICAKICBnLmNvdW50cyA8LSBhcy5tYXRyaXgoYWdncmVnYXRlKHg9ZHRtLm56LCBieSA9IGxpc3QoZ3JvdXBzPWdyb3VwcyksIEZVTj1zdW0pWywtMV0pCiAgCiAgaWYgKCFpcy5udWxsKHBhaXIpKSB7CiAgICBwci5kZWx0YSA8LSB0KHNjYWxlKCB0KHNjYWxlKGcubGFkdG1bcGFpcixdLCBjZW50ZXIgPSBULCBzY2FsZSA9RikpLCBjZW50ZXI9VCwgc2NhbGU9RikpCiAgICBwci5hZHRtX3cgPC0gLXN3ZWVwKGcuYWR0bVtwYWlyLF0sMSxyb3dTdW1zKGcuYWR0bVtwYWlyLF0pKQogICAgcHIuYWR0bV9rIDwtIC1zd2VlcChnLmFkdG1bcGFpcixdLDIsY29sU3VtcyhnLmFkdG1bcGFpcixdKSkgIyB3IHNwb2tlbiBieSBncm91cHMgb3RoZXIgdGhhbiBrCiAgICBwci5hZHRtX2t3IDwtIHN1bShnLmFkdG1bcGFpcixdKSAtIHByLmFkdG1fdyAtIHByLmFkdG1fayAtIGcuYWR0bVtwYWlyLF0gIyB0b3RhbCB0ZXJtcyBub3QgdyBvciBrCiAgICBwci5zZSA8LSBzcXJ0KDEvZy5hZHRtW3BhaXIsXSArIDEvcHIuYWR0bV93ICsgMS9wci5hZHRtX2sgKyAxL3ByLmFkdG1fa3cpCiAgICBwci56ZXRhIDwtIHByLmRlbHRhL3ByLnNlCiAgICAKICAgIHJldHVybihsaXN0KHpldGE9cHIuemV0YVsxLF0sIGRlbHRhPXByLmRlbHRhWzEsXSxzZT1wci5zZVsxLF0sIGNvdW50cyA9IGNvbFN1bXMoZHRtLm56KSwgYWNvdW50cyA9IGNvbFN1bXMoZy5hZHRtKSkpCiAgfSBlbHNlIHsKICAgIHJldHVybihsaXN0KHpldGE9Zy56ZXRhLGRlbHRhPWcuZGVsdGEsc2U9Zy5zZSxjb3VudHM9Zy5jb3VudHMsYWNvdW50cz1nLmFkdG0pKQogIH0KfQoKIyMjIyMjIyMjIyMjIyMgRklHSFRJTicgV09SRFMgUExPVFRJTkcgRlVOQ1RJT04KCiMgaGVscGVyIGZ1bmN0aW9uCm1ha2VUcmFuc3BhcmVudDwtZnVuY3Rpb24oc29tZUNvbG9yLCBhbHBoYT0xMDApCnsKICBuZXdDb2xvcjwtY29sMnJnYihzb21lQ29sb3IpCiAgYXBwbHkobmV3Q29sb3IsIDIsIGZ1bmN0aW9uKGN1cmNvbGRhdGEpe3JnYihyZWQ9Y3VyY29sZGF0YVsxXSwgZ3JlZW49Y3VyY29sZGF0YVsyXSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJsdWU9Y3VyY29sZGF0YVszXSxhbHBoYT1hbHBoYSwgbWF4Q29sb3JWYWx1ZT0yNTUpfSkKfQoKZncuZ2dwbG90Lmdyb3VwcyA8LSBmdW5jdGlvbihmdy5jaCwgZ3JvdXBzLnVzZSA9IGFzLmZhY3Rvcihyb3duYW1lcyhmdy5jaCR6ZXRhKSksIG1heC53b3JkcyA9IDUwLCBtYXguY291bnRyYW5rID0gNDAwLCBjb2xvcnBhbGV0dGU9cmVwKCJibGFjayIsbGVuZ3RoKGdyb3Vwcy51c2UpKSwgc2l6ZXNjYWxlPTIsIHRpdGxlPSJDb21wYXJpc29uIG9mIFRlcm1zIGJ5IEdyb3VwcyIsIHN1YnRpdGxlID0gIiIsIGNhcHRpb24gPSAiR3JvdXAtc3BlY2lmaWMgdGVybXMgYXJlIG9yZGVyZWQgYnkgRmlnaHRpbicgV29yZHMgc3RhdGlzdGljIChNb25yb2UsIGV0IGFsLiAyMDA4KSIpIHsKICBpZiAoaXMubnVsbChkaW0oZncuY2gkemV0YSkpKSB7IyMgdHdvLWdyb3VwIGZ3IG9iamVjdCBjb25zaXN0cyBvZiB2ZWN0b3JzLCBub3QgbWF0cmljZXMKICAgIHpldGFyYW5rbWF0IDwtIGNiaW5kKHJhbmsoLWZ3LmNoJHpldGEpLHJhbmsoZncuY2gkemV0YSkpCiAgICBjb2xuYW1lcyh6ZXRhcmFua21hdCkgPC0gZ3JvdXBzLnVzZQogICAgY291bnRyYW5rIDwtIHJhbmsoLShmdy5jaCRjb3VudHMpKQogIH0gZWxzZSB7CiAgICB6ZXRhcmFua21hdCA8LSBhcHBseSgtZncuY2gkemV0YVtncm91cHMudXNlLF0sMSxyYW5rKQogICAgY291bnRyYW5rIDwtIHJhbmsoLWNvbFN1bXMoZncuY2gkY291bnRzKSkKICB9CiAgd2lkZXBsb3RtYXQgPC0gYXNfdGliYmxlKGNiaW5kKHpldGFyYW5rbWF0LGNvdW50cmFuaz1jb3VudHJhbmspKQogIHdpZGVwbG90bWF0JHRlcm09bmFtZXMoY291bnRyYW5rKQogICNyYW5rcGxvdCA8LSBnYXRoZXIod2lkZXBsb3RtYXQsIHBhcnR5LCB6ZXRhcmFuaywgMTpuY29sKHpldGFyYW5rbWF0KSkKICByYW5rcGxvdCA8LSBnYXRoZXIod2lkZXBsb3RtYXQsIGdyb3Vwcy51c2UsIHpldGFyYW5rLCAxOm5jb2woemV0YXJhbmttYXQpKQogIHJhbmtwbG90JHBsb3RzaXplIDwtIHNpemVzY2FsZSooNTAvKHJhbmtwbG90JHpldGFyYW5rKSleKDEvNCkKICByYW5rcGxvdCA8LSByYW5rcGxvdFtyYW5rcGxvdCR6ZXRhcmFuayA8IG1heC53b3JkcyArIDEgJiByYW5rcGxvdCRjb3VudHJhbms8bWF4LmNvdW50cmFuaysxLF0KICByYW5rcGxvdCRncm91cHMudXNlIDwtIGZhY3RvcihyYW5rcGxvdCRncm91cHMudXNlLGxldmVscz1ncm91cHMudXNlKQogIAogIHAgPC0gZ2dwbG90KHJhbmtwbG90LCBhZXMoKG5yb3cocmFua3Bsb3QpLWNvdW50cmFuayleMSwgLSh6ZXRhcmFua14xKSwgY29sb3VyPWdyb3Vwcy51c2UpKSArIAogICAgZ2VvbV9wb2ludChzaG93LmxlZ2VuZD1GLHNpemU9c2l6ZXNjYWxlLzIpICsgCiAgICB0aGVtZV9jbGFzc2ljKCkgKwogICAgdGhlbWUoYXhpcy50aWNrcz1lbGVtZW50X2JsYW5rKCksIGF4aXMudGV4dD1lbGVtZW50X2JsYW5rKCkgKSArCiAgICB5bGltKC1tYXgud29yZHMsNDApICsKICAgIGZhY2V0X2dyaWQoZ3JvdXBzLnVzZSB+IC4pICsKICAgIGdlb21fdGV4dF9yZXBlbChhZXMobGFiZWwgPSB0ZXJtKSwgc2l6ZSA9IHJhbmtwbG90JHBsb3RzaXplLCBwb2ludC5wYWRkaW5nPS4wNSwKICAgICAgICAgICAgICAgICAgICBib3gucGFkZGluZyA9IHVuaXQoMC4yMCwgImxpbmVzIiksIHNob3cubGVnZW5kPUYsIG1heC5vdmVybGFwcyA9IEluZikgKwogICAgc2NhbGVfY29sb3VyX21hbnVhbCh2YWx1ZXMgPSBhbHBoYShjb2xvcnBhbGV0dGUsIC43KSkgKyAKIyAgICBsYWJzKHg9IlRlcm1zIHVzZWQgbW9yZSBmcmVxdWVudGx5IG92ZXJhbGwg4oaSIiwgeT0iVGVybXMgdXNlZCBtb3JlIGZyZXF1ZW50bHkgYnkgZ3JvdXAg4oaSIiwgIHRpdGxlPXRpdGxlLCBzdWJ0aXRsZT1zdWJ0aXRsZSAsIGNhcHRpb24gPSBjYXB0aW9uKSAKICAgIGxhYnMoeD1wYXN0ZSgiVGVybXMgdXNlZCBtb3JlIGZyZXF1ZW50bHkgb3ZlcmFsbCAtLT4iKSwgeT1wYXN0ZSgiVGVybXMgdXNlZCBtb3JlIGZyZXF1ZW50bHkgYnkgZ3JvdXAgLS0+IiksICB0aXRsZT10aXRsZSwgc3VidGl0bGU9c3VidGl0bGUgLCBjYXB0aW9uID0gY2FwdGlvbikgCiAgCn0KCm9wdGlvbnMoZ2dyZXBlbC5tYXgub3ZlcmxhcHMgPSBJbmYpCgpmdy5rZXlzIDwtIGZ1bmN0aW9uKGZ3LmNoLG4ua2V5cz0xMCkgewogIG4uZ3JvdXBzIDwtIG5yb3coZncuY2gkemV0YSkKICBrZXlzIDwtIG1hdHJpeCgiIixuLmtleXMsbi5ncm91cHMpCiAgY29sbmFtZXMoa2V5cykgPC0gcm93bmFtZXMoZncuY2gkemV0YSkKICAKICBmb3IgKGcgaW4gMTpuLmdyb3VwcykgewogICAga2V5c1ssZ10gPC0gbmFtZXMoc29ydChmdy5jaCR6ZXRhW2csXSxkZWM9VClbMTpuLmtleXNdKQogIH0KICBrZXlzCn0KYGBgCgoKIyMgQ29tcGFyZSBBc3NvY2lhdGVkIFByZXNzIDE5OTQtMjAxMDogQmVmb3JlIGFuZCBBZnRlciAiZXh0cmVtaXN0IiBhbmQgb3RoZXIgcXVlcnkgdGVybXMKCioqUXVlcnkgc2VhcmNoOioqCihhY3RpdmkqIHwgYWhiYXNoIHwgYWtyb21peWEgfCBhbmplbSB8IGFuc2hhcnV0IHwgYW50aWNhcGl0YWwqIHwgYW50aWRlbW9jciogfCBhbnRpZXN0YWJsaXNoKiB8IGFudGlmYSB8IGFudGlnb3Zlcm4qIHwgYW50aW1pbGl0YXIqIHwgYW50aW1vbmFyY2gqIHwgYW50aXBhdHJpKiB8IGFudGlyZWxpKiB8IGFudGlzZW0qIHwgYW50aXNvY2lhKiB8IGFudGlzeXN0KiB8IGFwb3N0KiB8IGF0aGFyaXMgfCBhdGhlaSogfCBhdGhlaXN0cyB8IGF1w59lcnBhcmxhbWVudGFyaSogfCBhdXRob3JpdGFyKiB8IGJhZ2F1KiB8IGJpZ290cyB8IGJwbGYgfCBidWtoYXJpKiB8IGNhcGl0dWxhdGlvKiB8IGNvbnNwaXJhdG8qIHwgY291bnRlcmoqIHwgY3liZXJjYWxpcCogfCBkYW1pZ28gfCBkYXdybyogfCBkZW1vbiogfCBkZXJhZGljYWxpemEqIHwgZGV2aWF0aW8qIHwgZGlxcWkgfCBkaXNzaWQqIHwgZGphbWFhdCB8IGRvdGJ1cyogfCBlY29mYXMqIHwgZXNwb3UqIHwgZXRobm9uYXRpb25hKiB8IGV4dHJlbSogfCBmYWNpc3RzIHwgZmFkYWlhKiB8IGZhbmF0KiB8IGZhc2NpKiB8IGZldMO2IHwgZmlnaHRkZW0qIHwgZnJlZWRvKiB8IGZ1bmRhbWVudGFsKiB8IGZ1cXJhIHwgZ2FmYXRhciB8IGdhbWVyZ2EqIHwgZ2VtaWR6aWkgfCBnaHVsdXd3IHwgZ2xvYmFsaSogfCBncmFtc2MqIHwgZ8O8bGVuKiB8IGhhY2t0aXYqIHwgaGFxdW5hIHwgaGFyZGxpbmUgfCBoYXJrYXR1bCB8IGhhdGVtb24qIHwgaGVpbXdlKiB8IGhlemJvbCogfCBoaW5kdXBoKiB8IGhpbmR1dHZhIHwgaGl6YnV0IHwgaG9qamEqIHwgaWRlb2xvZyogfCBpbmNpdGVtKiB8IGluY2l0ZXJzIHwgaW5zdXJyKiB8IGludGFjdGkqIHwgaXNsYW00dWsgfCBpc2xhbSogfCBqYWxqYWxhdCB8IGpiYWtjIHwgamloYWRpKiB8IGptamIgfCBqcnRuIHwganVkYWkqIHwganVoYXltYW4gfCBqdW5kdSogfCBrYWRpemEqIHwga2FoYW5pc20gfCBrYWhhbmlzdCB8IGthcnJhbSogfCBrYXlzYSogfCBraGFsaXMqIHwga2hhdG1pYSB8IGtoYXdhcmlqIHwga2hvbWVpbiogfCBrb3V0bGEgfCBsZWZ0aXN0IHwgbGVmdGlzdHMgfCBsZWZ0d2luZyB8IGxpYmVyYXRpbyogfCBtYWRraGEqIHwgbWFka2hhbCogfCBtYWltb25pZCogfCBtYW5vc3AqIHwgbWF1cmFzKiB8IG1jbG4gfCBtaWxpdGFuKiB8IG1pbGxhdHUgfCBtb25hcmMqIHwgbXVkamEqIHwgbXVoYWppKiB8IG11amFoaWQqIHwgbXVyYWIqIHwgbXV0dGFoaSogfCBuYWpqYWRhaCB8IG5hdGlvbmFsaSogfCBuZW9mYXMqIHwgbmVvbmEqIHwgb3BhbnRpc2ggfCBvcHBvc2l0aSogfCBwYWxlb2xpYmVydGFyKiB8IHBhcmFtaWxpKiB8IHBhcmxpYW1lbnRhKiB8IHBlZ2lkYSB8IHBvcHVsaXN0IHwgcHJpbmNpcGEqIHwgcHJvZmUqIHwgcHJvd2FyIHwgcHV0aW5pc3QgfCBxYWRhcmkgfCBxdXJhbmlzbSB8IHF1cmFuaXN0IHwgcXV0YmlzbSB8IHF1dGJpc3QgfCBxdXRiaXN0cyB8IHJhY2lzKiB8IHJhZGljYSogfCByZWFjdGlvbmkqIHwgcmVmb3JtaXMqIHwgcmVpY2hzYsO8cmdlcmJld2UqIHwgcmlnaHRpc3QgfCByaWdodHcqIHwgcm9maXEgfCBydXNzb3BoKiB8IHNhYmlyZWVuIHwgc2FkZGEqIHwgc2FsYWYqIHwgc2F5YWZmIHwgc2NyaXB0dXJhKiB8IHNlY3VsYSogfCBzZXBhcmF0aW9uaSogfCBzaGFyaWE0aG9sKiB8IHNpa3Jpa2ltIHwgc3BsaXQqIHwgc3F1YWRpc20gfCBzdHJhc3NlKiB8IHN1YnZlciogfCBzdWlkbGFuKiB8IHN1a2FybiogfCBzdXByZW1hKiB8IHN1cHJlbWFjKiB8IHN5bXBhdGhpKiB8IHRhYmxlKiB8IHRhYmxpcSB8IHRha2ZpciB8IHRha2ZpciogfCB0YWtmaSogfHRlcnJvciogfCB0aGVvYyogfCB0aXRvaXRlIHwgdHJpYmEqIHwgdHJvdHMqIHwgdHJvdHNrKiB8IHVrcmFpbm9waCogfCB1bHRyYWNvbnNlcnZhKiB8IHVsdHJhbGliKiB8IHVsdHJhbmF0aW9uYSogfCB1bHRyYXIqIHwgdXNjbW8gfCB3YWhhYmJpIHwgd2FoYWIqIHwgd2FoaGEqIHwgeGVub3BoKiB8IHl1bGRlKiB8IHppbm92aWUqKQoKCioqTG9hZCBhbmQgY2xlYW4gdGhlIGRhdGEqKgoKICAqIHRvIHN0cmluZyAmIGxvd2VyIHRleHQKICAqIHBpdm90IHRvIGxvbmcgZm9ybWF0CiAgKiBhcHBseSB0ZXh0X2NsZWFuZXIgdG8gb25lIGNvbHVtbiAiY29udGV4dC50ZXh0IgoKYGBge3IsICByZXN1bHRzPSdhc2lzJ30KdGV4dF9jbGVhbmVyPC1mdW5jdGlvbihjb3JwdXMpewogIHRlbXBjb3JwdXM8LUNvcnB1cyhWZWN0b3JTb3VyY2UoY29ycHVzKSkKICB0ZW1wY29ycHVzPC10bV9tYXAodGVtcGNvcnB1cywKICAgICAgICAgICAgICAgICAgICByZW1vdmVQdW5jdHVhdGlvbikKICB0ZW1wY29ycHVzPC10bV9tYXAodGVtcGNvcnB1cywKICAgICAgICAgICAgICAgICAgICBzdHJpcFdoaXRlc3BhY2UpCiAgdGVtcGNvcnB1czwtdG1fbWFwKHRlbXBjb3JwdXMsCiAgICAgICAgICAgICAgICAgICAgcmVtb3ZlTnVtYmVycykKICB0ZW1wY29ycHVzPC10bV9tYXAodGVtcGNvcnB1cywKICAgICAgICAgICAgICAgICAgICAgcmVtb3ZlV29yZHMsIHN0b3B3b3JkcygiZW5nbGlzaCIpKQogIHRlbXBjb3JwdXM8LXRtX21hcCh0ZW1wY29ycHVzLCAKICAgICAgICAgICAgICAgICAgICBzdGVtRG9jdW1lbnQpCiAgcmV0dXJuKHRlbXBjb3JwdXMpCn0KCmBgYAoKYGBge3IsIGVjaG89RkFMU0UsIHJlc3VsdHM9IEZBTFNFfQoKZXh0cmVtX05ZVC5kZm1fYWxsIDwtcmVhZC5kZWxpbSgifi9Eb2N1bWVudHMvR2l0SHViLzcwY29ycl9leHRyZW1pc3RfOTQxMF9OWVQudHh0IiwgaGVhZGVyPVRSVUUsIHNlcD0iXHQiKQpleHRyZW1fTllULmRmbV9hbGwkcHViZGF0ZSA9IHN1YnN0cihleHRyZW1fTllULmRmbV9hbGwkVGV4dC5JRCw5LDE2KQpleHRyZW1fTllULmRmbV9hbGwkcHViZGF0ZSA8LSBhcy5QT1NJWGN0KGV4dHJlbV9OWVQuZGZtX2FsbCRwdWJkYXRlLCBmb3JtYXQgPSAiJVklbSVkIikKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpzZXQuc2VlZCgyMjE3KQpleHRyZW1fTllULmRmbSA8LSBhcy5kYXRhLmZyYW1lKHNhbXBsZV9uKGV4dHJlbV9OWVQuZGZtX2FsbCwgMTUwMDApKQpybShleHRyZW1fTllULmRmbV9hbGwpCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKIyBleHRyZW1fTllULmRmbSRDb250ZXh0LmJlZm9yZSA9IGxhcHBseShleHRyZW1fTllULmRmbSRDb250ZXh0LmJlZm9yZSwgdG9TdHJpbmcpCiMgZXh0cmVtX05ZVC5kZm0kQ29udGV4dC5iZWZvcmUgPSBsYXBwbHkoZXh0cmVtX05ZVC5kZm0kQ29udGV4dC5iZWZvcmUsIHRvbG93ZXIpCiMgCiMgZXh0cmVtX05ZVC5kZm0kQ29udGV4dC5hZnRlciA9IGxhcHBseShleHRyZW1fTllULmRmbSRDb250ZXh0LmFmdGVyLCB0b1N0cmluZykKIyBleHRyZW1fTllULmRmbSRDb250ZXh0LmFmdGVyID0gbGFwcGx5KGV4dHJlbV9OWVQuZGZtJENvbnRleHQuYWZ0ZXIsIHRvbG93ZXIpCgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKCmV4dHJlbV9OWVQuZGZtIDwtIGV4dHJlbV9OWVQuZGZtICU+JSBkaXN0aW5jdChDb250ZXh0LmJlZm9yZSwgLmtlZXBfYWxsID0gVFJVRSkKCmV4dHJlbV9OWVQuZGZtLmxvbmcgPC0gcGl2b3RfbG9uZ2VyKGV4dHJlbV9OWVQuZGZtLCBjb2xzPWMoQ29udGV4dC5iZWZvcmUsIENvbnRleHQuYWZ0ZXIpLCBuYW1lc190byA9ICJDb250ZXh0IiwgdmFsdWVzX3RvID0gImNvbnRleHQudGV4dCIpCgpleHRyZW1fTllULmRmbS5sb25nJENvbnRleHQgPC0gYXMuZmFjdG9yKGV4dHJlbV9OWVQuZGZtLmxvbmckQ29udGV4dCkKCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKZXh0cmVtZWNvcnB1cyA8LXRleHRfY2xlYW5lcihleHRyZW1fTllULmRmbS5sb25nJGNvbnRleHQudGV4dCkKCmBgYAoKCkNhbGN1bGF0ZSBGVy4KCmBgYHtyLCBtZXNzYWdlPUZBTFNFfQoKZSA8LSBkZm0oZXh0cmVtZWNvcnB1cyRjb250ZW50KQptZXNzYWdlKGRpbShlKSkKaGVhZChlKQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgplIDwtIGRmbV9zZWxlY3QoZSwgcGF0dGVybiA9IHN0b3B3b3JkcygiZW5nbGlzaCIpLCBzZWxlY3Rpb24gPSAicmVtb3ZlIikKZSA8LSBkZm1fc2VsZWN0KGUsIG1pbl9uY2hhciA9IDIpCmUgPC0gZGZtX3RyaW0oZSwgbWluX3Rlcm1mcmVxID0gNCwgbWluX2RvY2ZyZXEgPSAuMDUsIHZlcmJvc2U9VFJVRSkKCiNkaW0oZSkKIyBzcGFyc2l0eShlKQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpleHRyZW1fZHRtIDwtIGNvbnZlcnQoZSwgdG89J2RhdGEuZnJhbWUnKQpleHRyZW1fZHRtIDwtIGV4dHJlbV9kdG1bLWMoMSldCncgPC0gd2hpY2goIHNhcHBseShleHRyZW1fZHRtLCBjbGFzcyApID09ICdjaGFyYWN0ZXInICkKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwoKZncuZXh0cmVtIDwtIGZ3Z3JvdXBzKGV4dHJlbV9kdG0sIGdyb3Vwcz1leHRyZW1fTllULmRmbS5sb25nJENvbnRleHQpCgpybShleHRyZW1fZHRtKQoKYGBgCgoKKipHZXQgYW5kIHNob3cgdGhlIHRvcCB3b3JkcyBwZXIgZ3JvdXAgYnkgemV0YS4qKgoKYGBge3IgZWNobz1UUlVFLCByZXN1bHRzPSJhc2lzIn0KCmZ3a2V5cy5leHRyZW0gPC0gZncua2V5cyhmdy5leHRyZW0sIG4ua2V5cz0yMCkKY29scyA8LSByZXYoY29sbmFtZXMoZndrZXlzLmV4dHJlbSkpCmZ3a2V5cy5leHRyZW0gPC0gZndrZXlzLmV4dHJlbVssY29sc10Ka2FibGUoZndrZXlzLmV4dHJlbSkKCmBgYAoKUGxvdDogQmVmb3JlIGluIEJsdWUsIEFmdGVyIGluIFJlZAoKYGBge3IsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTR9CgpwLmZ3LmV4dHJlbSA8LSBmdy5nZ3Bsb3QuZ3JvdXBzKGZ3LmV4dHJlbSxzaXplc2NhbGU9NCxtYXgud29yZHM9MjAwLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIG1heC5jb3VudHJhbms9NDAwLGNvbG9ycGFsZXR0ZT1jKCJyZWQiLCJibHVlIiksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAnQ29tcGFyaXNvbiBvZiBUZXJtcyBCZWZvcmUgYW5kIEFmdGVyIFF1ZXJ5IFdvcmQnKQpwLmZ3LmV4dHJlbQpgYGAKCiMjIENhbGN1bGF0ZSBieSBxdWVyeSBpdGVtL3NlYXJjaCB0ZXJtCgpgYGB7ciwgbWVzc2FnZT1GQUxTRSwgZmlnLmhlaWdodD04LCBmaWcud2lkdGg9NH0KCmV4dHJlbV9OWVQuZGZtLmxvbmckUXVlcnkuaXRlbSA8LSBhcy5mYWN0b3IoZXh0cmVtX05ZVC5kZm0ubG9uZyRRdWVyeS5pdGVtKQoKdG9wX24gPC1hcy5kYXRhLmZyYW1lKHNvcnQodGFibGUoZXh0cmVtX05ZVC5kZm0ubG9uZyRRdWVyeS5pdGVtKSwgZGVjcmVhc2luZyA9IFRSVUUpWzE6NV0pIAptZXNzYWdlKGRpbSh0b3BfbikpCmNvbG5hbWVzKHRvcF9uKSA8LSBjKCd0ZXJtJywgJ0ZyZXEnKQptZXNzYWdlKHRvcF9uKQoKZXh0cmVtX2R0bV90b3BuIDwtIGNvbnZlcnQoZSwgdG89J2RhdGEuZnJhbWUnKQpleHRyZW1fZHRtX3RvcG4kTnVtYmVyLm9mLmhpdCA8LSBleHRyZW1fTllULmRmbS5sb25nJE51bWJlci5vZi5oaXQKCnRvcG5fdGVybXMgPC0gZXh0cmVtX05ZVC5kZm0ubG9uZyAlPiUKICAgICAgZmlsdGVyKFF1ZXJ5Lml0ZW0gJWluJSB0b3BfbiR0ZXJtKQoKZXh0cmVtX2R0bV90b3BuX2tlZXAgPC0gZXh0cmVtX2R0bV90b3BuICU+JSAKICAgIGZpbHRlcihOdW1iZXIub2YuaGl0ICVpbiUgdG9wbl90ZXJtcyROdW1iZXIub2YuaGl0KQoKciA8LSBzdW0obGVuZ3RoKGV4dHJlbV9kdG1fdG9wbl9rZWVwKSkKCmV4dHJlbV9kdG1fdG9wbl9rZWVwIDwtIGV4dHJlbV9kdG1fdG9wbl9rZWVwWy1jKDEsIHIpXQoKZncucXVlcnlfaXRlbSA8LSBmd2dyb3VwcyhleHRyZW1fZHRtX3RvcG5fa2VlcCxncm91cHMgPSB0b3BuX3Rlcm1zJFF1ZXJ5Lml0ZW0pCmZ3a2V5cy5xdWVyeV9pdGVtIDwtIGZ3LmtleXMoZncucXVlcnlfaXRlbSwgbi5rZXlzPTE1KQprYWJsZShmd2tleXMucXVlcnlfaXRlbSkKCnJtKHIpCnJtKGV4dHJlbV9kdG1fdG9wbikKCiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpwLmZ3LnF1ZXJ5X2l0ZW0gPC0gZncuZ2dwbG90Lmdyb3Vwcyhmdy5xdWVyeV9pdGVtLHNpemVzY2FsZT0zLjIsbWF4LndvcmRzPTE1MCxtYXguY291bnRyYW5rPTQwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY29sb3JwYWxldHRlPWMoImRhcmtncmVlbiIsImRhcmtncmVlbiIsImRhcmtncmVlbiIsImRhcmtncmVlbiIsImRhcmtncmVlbiIpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB0aXRsZSA9ICdDb21wYXJpc29uIG9mIFRlcm1zIGJ5IE92ZXJhbGwgVG9wIFRlcm1zJykKcC5mdy5xdWVyeV9pdGVtCgpgYGAKCgoKYGBge3IsIG1lc3NhZ2U9RkFMU0UsIGZpZy5oZWlnaHQ9NSwgZmlnLndpZHRoPTR9CgpleHRyZW1fZHRtX3RvcG4gPC0gY29udmVydChlLCB0bz0nZGF0YS5mcmFtZScpCmV4dHJlbV9kdG1fdG9wbiROdW1iZXIub2YuaGl0IDwtIGV4dHJlbV9OWVQuZGZtLmxvbmckTnVtYmVyLm9mLmhpdApleHRyZW1fZHRtX3RvcG4kQ29udGV4dCA8LSBleHRyZW1fTllULmRmbS5sb25nJENvbnRleHQKCnRvcG5fdGVybXMgPC0gZXh0cmVtX05ZVC5kZm0ubG9uZyAlPiUKICAgICAgZmlsdGVyKFF1ZXJ5Lml0ZW0gJWluJSB0b3BfbiR0ZXJtKQoKZXh0cmVtX2R0bV90b3BuX2tlZXAgPC0gZXh0cmVtX2R0bV90b3BuICU+JSAKICAgIGZpbHRlcihOdW1iZXIub2YuaGl0ICVpbiUgdG9wbl90ZXJtcyROdW1iZXIub2YuaGl0KQoKZXh0cmVtX2R0bV90b3BuX2tlZXBfYmVmb3JlIDwtIGV4dHJlbV9kdG1fdG9wbl9rZWVwW2dyZXAoImJlZm9yZSIsZXh0cmVtX2R0bV90b3BuX2tlZXAkQ29udGV4dCksXQpleHRyZW1fZHRtX3RvcG5fa2VlcF9hZnRlciA8LSBleHRyZW1fZHRtX3RvcG5fa2VlcFtncmVwKCJhZnRlciIsIGV4dHJlbV9kdG1fdG9wbl9rZWVwJENvbnRleHQpLF0KCnJyIDwtZGltKHRvcG5fdGVybXMpWzFdCnIgPC0gc3VtKGxlbmd0aChleHRyZW1fZHRtX3RvcG5fa2VlcF9iZWZvcmUpKQp0b3BuX3Rlcm1zX2JlZm9yZSA8LSB0b3BuX3Rlcm1zW3NlcSgxLHJyLDIpLF0KdG9wbl90ZXJtc19hZnRlciA8LSB0b3BuX3Rlcm1zW3NlcSgyLHJyLDIpLF0KCmV4dHJlbV9kdG1fdG9wbl9rZWVwX2JlZm9yZSA8LSBleHRyZW1fZHRtX3RvcG5fa2VlcF9iZWZvcmVbLWMoMSwgci0xLCByKV0KZXh0cmVtX2R0bV90b3BuX2tlZXBfYWZ0ZXIgPC0gZXh0cmVtX2R0bV90b3BuX2tlZXBfYWZ0ZXJbLWMoMSwgci0xLCByKV0KCnJtKHJyKQpybShyKQpybShleHRyZW1fZHRtX3RvcG4pCgojIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMKZncucXVlcnlfaXRlbV9iZWZvcmUgPC0gZndncm91cHMoZXh0cmVtX2R0bV90b3BuX2tlZXBfYmVmb3JlLGdyb3VwcyA9IHRvcG5fdGVybXNfYmVmb3JlJFF1ZXJ5Lml0ZW0pCmZ3a2V5cy5xdWVyeV9pdGVtX2JlZm9yZSA8LSBmdy5rZXlzKGZ3LnF1ZXJ5X2l0ZW1fYmVmb3JlLCBuLmtleXM9MTUpCmthYmxlKGZ3a2V5cy5xdWVyeV9pdGVtX2JlZm9yZSwgY2FwdGlvbiA9ICJUb3AgMTUgV29yZHMgZm9yIFF1ZXJ5IFRlcm06IEJFRk9SRSIpCgpwLmZ3LnF1ZXJ5X2l0ZW1fYmVmb3JlIDwtIGZ3LmdncGxvdC5ncm91cHMoZncucXVlcnlfaXRlbV9iZWZvcmUsc2l6ZXNjYWxlPTIsbWF4LndvcmRzPTE1MCxtYXguY291bnRyYW5rPTQwMCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNvbG9ycGFsZXR0ZSA9IGMoJ2JsdWUnLCdibHVlJywnYmx1ZScsICdibHVlJywnYmx1ZScpLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgdGl0bGUgPSAnQ29tcGFyaXNvbiBvZiBUZXJtcyBieSBPdmVyYWxsIFRvcCBUZXJtczogQkVGT1JFJykKcC5mdy5xdWVyeV9pdGVtX2JlZm9yZQoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCmZ3LnF1ZXJ5X2l0ZW1fYWZ0ZXIgPC0gZndncm91cHMoZXh0cmVtX2R0bV90b3BuX2tlZXBfYWZ0ZXIsZ3JvdXBzID0gdG9wbl90ZXJtc19hZnRlciRRdWVyeS5pdGVtKQpmd2tleXMucXVlcnlfaXRlbV9hZnRlciA8LSBmdy5rZXlzKGZ3LnF1ZXJ5X2l0ZW1fYWZ0ZXIsIG4ua2V5cz0xNSkKa2FibGUoZndrZXlzLnF1ZXJ5X2l0ZW1fYWZ0ZXIsIGNhcHRpb24gPSAiVG9wIDE1IFdvcmRzIGZvciBRdWVyeSBUZXJtOiBBRlRFUiIpCgpwLmZ3LnF1ZXJ5X2l0ZW1fYWZ0ZXIgPC0gZncuZ2dwbG90Lmdyb3Vwcyhmdy5xdWVyeV9pdGVtX2FmdGVyLHNpemVzY2FsZT0yLG1heC53b3Jkcz0xNTAsbWF4LmNvdW50cmFuaz00MDAsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjb2xvcnBhbGV0dGUgPSBjKCdyZWQnLCAncmVkJywncmVkJywncmVkJywncmVkJyksCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpdGxlID0gJ0NvbXBhcmlzb24gb2YgVGVybXMgYnkgT3ZlcmFsbCBUb3AgVGVybXM6IEFGVEVSJykKcC5mdy5xdWVyeV9pdGVtX2FmdGVyCgoKYGBgCgoKCiMjIENhbGN1bGF0ZSBQYXJ0cyBvZiBzcGVlY2ggYnkgYmVmb3JlIGFuZCBhZnRlcgoKQ2FsY3VsYXRlIEZXIGFuZCBrZXlzCmBgYHtyLCByZXN1bHRzPSdoaWRlJywgd2FybmluZz1GQUxTRX0KCnVkX21vZGVsIDwtIHVkcGlwZV9sb2FkX21vZGVsKHVkX21vZGVsJGZpbGVfbW9kZWwpCgp0eHQgPC1hcy5jaGFyYWN0ZXIoZXh0cmVtX05ZVC5kZm0ubG9uZyRjb250ZXh0LnRleHQpCgp4X3VkcCA8LSB1ZHBpcGVfYW5ub3RhdGUodWRfbW9kZWwsIHggPSB0eHQsIGRvY19pZCA9IHNlcV9hbG9uZyh0eHQpKQp4IDwtIGFzLmRhdGEuZnJhbWUoeF91ZHApCgp4JGRvY19pZCA8LWFzLmludGVnZXIoeCRkb2NfaWQpCgp4X29kZC5iZWZvcmUgPC0geFt4JGRvY19pZCAlJSAyID09IDEsXQp4X2V2ZW4uYWZ0ZXIgPC14W3gkZG9jX2lkICUlIDIgPT0gMCwgXQoKYGBgCgoKKkEgZmV3IGJhcmNoYXJ0IGZ1bmN0aW9ucyoKCmBgYHtyLCByZXN1bHRzPSdoaWRlJ30KCiMjIFVOSVZFUlNBTCBQb1MKVVBPU19iYXJjaGFydCA8LSBmdW5jdGlvbihkZjEsIGRmMil7CiAgc3RhdHMxIDwtIHR4dF9mcmVxKGRmMSR1cG9zKQogIHN0YXRzMSRrZXkgPC0gZmFjdG9yKHN0YXRzMSRrZXksIGxldmVscyA9IHJldihzdGF0czEka2V5KSkKICAKICBzdGF0czIgPC0gdHh0X2ZyZXEoZGYyJHVwb3MpCiAgc3RhdHMyJGtleSA8LSBmYWN0b3Ioc3RhdHMyJGtleSwgbGV2ZWxzID0gcmV2KHN0YXRzMiRrZXkpKQogIAogIGMoYmFyY2hhcnQoa2V5IH4gZnJlcSwgZGF0YSA9IHN0YXRzMSwgY29sID0gImNhZGV0Ymx1ZSIsIAogICAgICAgIG1haW4gPSAiVVBPUyAoVW5pdmVyc2FsIFBhcnRzIG9mIFNwZWVjaClcbiBmcmVxdWVuY3kgb2Ygb2NjdXJyZW5jZTogQkVGT1JFIHZzIEFGVEVSIiwgCiAgICAgICAgIHhsYWIgPSAiRnJlcSIpLCAKICAgIGJhcmNoYXJ0KGtleSB+IGZyZXEsIGRhdGEgPSBzdGF0czIsIGNvbCA9ICAnc2t5Ymx1ZScsCiAgICAgICAgIHhsYWIgPSAiRnJlcSIpKQp9CgoKCiMjIE5PVU5TCk5PVU5TX2JhcmNoYXJ0IDwtIGZ1bmN0aW9uKGRmMSwgZGYyKXsKICAKICBzdGF0czEgPC0gc3Vic2V0KGRmMSwgdXBvcyAlaW4lIGMoIk5PVU4iKSkgCiAgc3RhdHMxIDwtIHR4dF9mcmVxKHN0YXRzMSR0b2tlbikKICBzdGF0czEka2V5IDwtIGZhY3RvcihzdGF0czEka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMxJGtleSkpCiAgCiAgc3RhdHMyIDwtIHN1YnNldChkZjIsIHVwb3MgJWluJSBjKCJOT1VOIikpIAogIHN0YXRzMiA8LSB0eHRfZnJlcShzdGF0czIkdG9rZW4pCiAgc3RhdHMyJGtleSA8LSBmYWN0b3Ioc3RhdHMyJGtleSwgbGV2ZWxzID0gcmV2KHN0YXRzMiRrZXkpKQogIAogIGMoYmFyY2hhcnQoa2V5IH4gZnJlcSwgZGF0YSA9IGhlYWQoc3RhdHMxLCAyMCksIGNvbCA9ICJjYWRldGJsdWUiLCAKICAgICAgICAgICBtYWluID0gIk1vc3Qgb2NjdXJyaW5nIG5vdW5zOiBCRUZPUkUgdnMgQUZURVIiLCB4bGFiID0gIkZyZXEiKSwKICAgICAgYmFyY2hhcnQoa2V5IH4gZnJlcSwgZGF0YSA9IGhlYWQoc3RhdHMyLCAyMCksIGNvbCA9ICJza3libHVlIiwgCiAgICAgICAgICAgIHhsYWIgPSAiRnJlcSIpKQp9CgojIyBBREpFQ1RJVkVTCkFESl9iYXJjaGFydCA8LSBmdW5jdGlvbihkZjEsIGRmMil7CiAgCiAgc3RhdHMxIDwtIHN1YnNldChkZjEsIHVwb3MgJWluJSBjKCJBREoiKSkgCiAgc3RhdHMxIDwtIHR4dF9mcmVxKHN0YXRzMSR0b2tlbikKICBzdGF0czEka2V5IDwtIGZhY3RvcihzdGF0czEka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMxJGtleSkpCiAgCiAgc3RhdHMyIDwtIHN1YnNldChkZjIsIHVwb3MgJWluJSBjKCJBREoiKSkgCiAgc3RhdHMyIDwtIHR4dF9mcmVxKHN0YXRzMiR0b2tlbikKICBzdGF0czIka2V5IDwtIGZhY3RvcihzdGF0czIka2V5LCBsZXZlbHMgPSByZXYoc3RhdHMyJGtleSkpCiAgCiAgYyhiYXJjaGFydChrZXkgfiBmcmVxLCBkYXRhID0gaGVhZChzdGF0czEsIDIwKSwgY29sID0gImNhZGV0Ymx1ZSIsIAogICAgICAgICAgIG1haW4gPSAiTW9zdCBvY2N1cnJpbmcgYWRqZWN0aXZlczogQkVGT1JFIHZzIEFGVEVSIiwgeGxhYiA9ICJGcmVxIiksCiAgICAgIGJhcmNoYXJ0KGtleSB+IGZyZXEsIGRhdGEgPSBoZWFkKHN0YXRzMiwgMjApLCBjb2wgPSAic2t5Ymx1ZSIsIAogICAgICAgICB4bGFiID0gIkZyZXEiKSkKfQoKIyMgVXNpbmcgUkFLRSB0byBmaW5kIGtleXdvcmRzClJBS0VfS1dfYmFyY2hhcnQgPC0gZnVuY3Rpb24oZGYxLGRmMil7CiAgCiAgc3RhdHMxIDwtIGtleXdvcmRzX3Jha2UoeCA9IGRmMSwgdGVybSA9ICJsZW1tYSIsIGdyb3VwID0gImRvY19pZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgcmVsZXZhbnQgPSBkZjEkdXBvcyAlaW4lIGMoIk5PVU4iLCAiQURKIikpCiAgc3RhdHMxJGtleSA8LSBmYWN0b3Ioc3RhdHMxJGtleXdvcmQsIGxldmVscyA9IHJldihzdGF0czEka2V5d29yZCkpCiAgCiAgc3RhdHMyIDwtIGtleXdvcmRzX3Jha2UoeCA9IGRmMiwgdGVybSA9ICJsZW1tYSIsIGdyb3VwID0gImRvY19pZCIsIAogICAgICAgICAgICAgICAgICAgICAgICAgcmVsZXZhbnQgPSBkZjIkdXBvcyAlaW4lIGMoIk5PVU4iLCAiQURKIikpCiAgc3RhdHMyJGtleSA8LSBmYWN0b3Ioc3RhdHMyJGtleXdvcmQsIGxldmVscyA9IHJldihzdGF0czIka2V5d29yZCkpCiAgCiAgCiAgYyhiYXJjaGFydChrZXkgfiByYWtlLCBkYXRhID0gaGVhZChzdWJzZXQoc3RhdHMxLCBmcmVxID4gMyksIDIwKSwgY29sID0gImNhZGV0Ymx1ZSIsIAogICAgICAgICAgIG1haW4gPSAiS2V5d29yZHMgaWRlbnRpZmllZCBieSBSQUtFOiBCRUZPUkUgdnMgQUZURVIiLCAKICAgICAgICAgICB4bGFiID0gIlJha2UiKSwKICAgIGJhcmNoYXJ0KGtleSB+IHJha2UsIGRhdGEgPSBoZWFkKHN1YnNldChzdGF0czIsIGZyZXEgPiAzKSwgMjApLCBjb2wgPSAic2t5Ymx1ZSIsIAogICAgICAgICAgIHhsYWIgPSAiUmFrZSIpKQp9CgojIyBVc2luZyBQb2ludHdpc2UgTXV0dWFsIEluZm9ybWF0aW9uIENvbGxvY2F0aW9ucwpQV0lfYmFyY2hhcnQgPC0gZnVuY3Rpb24oZGYxLCBkZjIpewogIAogIGRmMSR3b3JkIDwtIHRvbG93ZXIoZGYxJHRva2VuKQogIHN0YXRzMSA8LSBrZXl3b3Jkc19jb2xsb2NhdGlvbih4ID0gZGYxLCB0ZXJtID0gIndvcmQiLCBncm91cCA9ICJkb2NfaWQiKQogIHN0YXRzMSRrZXkgPC0gZmFjdG9yKHN0YXRzMSRrZXl3b3JkLCBsZXZlbHMgPSByZXYoc3RhdHMxJGtleXdvcmQpKQogIAogIGRmMiR3b3JkIDwtIHRvbG93ZXIoZGYyJHRva2VuKQogIHN0YXRzMiA8LSBrZXl3b3Jkc19jb2xsb2NhdGlvbih4ID0gZGYyLCB0ZXJtID0gIndvcmQiLCBncm91cCA9ICJkb2NfaWQiKQogIHN0YXRzMiRrZXkgPC0gZmFjdG9yKHN0YXRzMiRrZXl3b3JkLCBsZXZlbHMgPSByZXYoc3RhdHMyJGtleXdvcmQpKQogIAogIGMoYmFyY2hhcnQoa2V5IH4gcG1pLCBkYXRhID0gaGVhZChzdWJzZXQoc3RhdHMxLCBmcmVxID4gMyksIDIwKSwgY29sID0gImNhZGV0Ymx1ZSIsIAogICAgICAgICAgIG1haW4gPSAiS2V5d29yZHMgaWRlbnRpZmllZCBieSBQTUkgQ29sbG9jYXRpb246IEJFRk9SRSB2cyBBRlRFUiIsIAogICAgICAgICAgIHhsYWIgPSAiUE1JIChQb2ludHdpc2UgTXV0dWFsIEluZm9ybWF0aW9uKSIpLAogICAgICBiYXJjaGFydChrZXkgfiBwbWksIGRhdGEgPSBoZWFkKHN1YnNldChzdGF0czIsIGZyZXEgPiAzKSwgMjApLCBjb2wgPSAic2t5Ymx1ZSIsIAogICAgICAgICAgIHhsYWIgPSAiUE1JIChQb2ludHdpc2UgTXV0dWFsIEluZm9ybWF0aW9uKSIpKQp9CgojIyBVc2luZyBhIHNlcXVlbmNlIG9mIFBPUyB0YWdzIChub3VuIHBocmFzZXMgLyB2ZXJiIHBocmFzZXMpClBPU19iYXJjaGFydCA8LSBmdW5jdGlvbihkZjEsIGRmMil7CiAgCiAgZGYxJHBocmFzZV90YWcgPC0gYXNfcGhyYXNlbWFjaGluZShkZjEkdXBvcywgdHlwZSA9ICJ1cG9zIikKICBzdGF0czEgPC0ga2V5d29yZHNfcGhyYXNlcyh4ID0gZGYxJHBocmFzZV90YWcsIHRlcm0gPSB0b2xvd2VyKGRmMSR0b2tlbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICIoQXxOKSpOKFArRCooQXxOKSpOKSoiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzX3JlZ2V4ID0gVFJVRSwgZGV0YWlsZWQgPSBGQUxTRSkKICBzdGF0czEgPC0gc3Vic2V0KHN0YXRzMSwgbmdyYW0gPiAxICYgZnJlcSA+IDMpCiAgc3RhdHMxJGtleSA8LSBmYWN0b3Ioc3RhdHMxJGtleXdvcmQsIGxldmVscyA9IHJldihzdGF0czEka2V5d29yZCkpCiAgCiAgZGYyJHBocmFzZV90YWcgPC0gYXNfcGhyYXNlbWFjaGluZShkZjIkdXBvcywgdHlwZSA9ICJ1cG9zIikKICBzdGF0czIgPC0ga2V5d29yZHNfcGhyYXNlcyh4ID0gZGYyJHBocmFzZV90YWcsIHRlcm0gPSB0b2xvd2VyKGRmMiR0b2tlbiksIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGF0dGVybiA9ICIoQXxOKSpOKFArRCooQXxOKSpOKSoiLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGlzX3JlZ2V4ID0gVFJVRSwgZGV0YWlsZWQgPSBGQUxTRSkKICBzdGF0czIgPC0gc3Vic2V0KHN0YXRzMiwgbmdyYW0gPiAxICYgZnJlcSA+IDMpCiAgc3RhdHMyJGtleSA8LSBmYWN0b3Ioc3RhdHMyJGtleXdvcmQsIGxldmVscyA9IHJldihzdGF0czIka2V5d29yZCkpCiAgCiAgYyhiYXJjaGFydChrZXkgfiBmcmVxLCBkYXRhID0gaGVhZChzdGF0czEsIDIwKSwgY29sID0gImNhZGV0Ymx1ZSIsIAogICAgICAgICAgIG1haW4gPSAiS2V5d29yZHMgLSBzaW1wbGUgbm91biBwaHJhc2VzOiBCRUZPUkUgdnMgQUZURVIiLCB4bGFiID0gIkZyZXF1ZW5jeSIpLAogICAgICBiYXJjaGFydChrZXkgfiBmcmVxLCBkYXRhID0gaGVhZChzdGF0czIsIDIwKSwgY29sID0gInNreWJsdWUiLCAKICAgICAgICAgICAgICAgeGxhYiA9ICJGcmVxdWVuY3kiKSkKfQpgYGAKCgojIyBCYXIgQ2hhcnRzIGZyb20gRnVuY3Rpb25zIEFib3ZlCgpgYGB7ciBQT1NiYXJjaGFydHMsIGVjaG89VFJVRSwgZmlnLndpZHRoPTh9CgpVUE9TX2JhcmNoYXJ0KHhfb2RkLmJlZm9yZSwgeF9ldmVuLmFmdGVyKQpOT1VOU19iYXJjaGFydCh4X29kZC5iZWZvcmUsIHhfZXZlbi5hZnRlcikKQURKX2JhcmNoYXJ0KHhfb2RkLmJlZm9yZSwgeF9ldmVuLmFmdGVyKQpSQUtFX0tXX2JhcmNoYXJ0KHhfb2RkLmJlZm9yZSwgeF9ldmVuLmFmdGVyKQpQV0lfYmFyY2hhcnQoeF9vZGQuYmVmb3JlLCB4X2V2ZW4uYWZ0ZXIpClBPU19iYXJjaGFydCh4X29kZC5iZWZvcmUsIHhfZXZlbi5hZnRlcikKCmBgYAoKCiMjIENvb2NjdXJlbmNlcwoKYGBge3IsIGVjaG89RkFMU0UsIGZpZy53aWR0aD04fQoKQ09fT0Nfbm91bl9hZGpfc2FtZV9zZW50LmJlZm9yZSA8LSBmdW5jdGlvbihkZjEpewogIAogIGxpYnJhcnkoaWdyYXBoKQogIGxpYnJhcnkoZ2dyYXBoKQogIGxpYnJhcnkoZ2dwbG90MikKICAKICBjb29jIDwtIGNvb2NjdXJyZW5jZSh4ID0gc3Vic2V0KGRmMSwgdXBvcyAlaW4lIGMoIk5PVU4iLCAiQURKIikpLCAKICAgICAgICAgICAgICAgICAgICAgICB0ZXJtID0gImxlbW1hIiwgCiAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBjKCJkb2NfaWQiLCAicGFyYWdyYXBoX2lkIiwgInNlbnRlbmNlX2lkIikpCgogIHdvcmRuZXR3b3JrIDwtIGhlYWQoY29vYywgNjApCiAgd29yZG5ldHdvcmsgPC0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKHdvcmRuZXR3b3JrKQogIAogIGdncmFwaCh3b3JkbmV0d29yaywgbGF5b3V0ID0gImZyIikgKwogICAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gY29vYywgZWRnZV9hbHBoYSA9IGNvb2MpLCBlZGdlX2NvbG91ciA9ICJwaW5rIikgKwogICAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIGNvbCA9ICJkYXJrZ3JlZW4iLCBzaXplID0gNCkgKwogICAgdGhlbWVfZ3JhcGgoYmFzZV9mYW1pbHkgPSAiQXJpYWwgTmFycm93IikgKwogICAgdGhlbWUobGVnZW5kLnBvc2l0aW9uID0gIm5vbmUiKSArCiAgICBsYWJzKHRpdGxlID0gIkNvb2NjdXJyZW5jZXMgd2l0aGluIHNlbnRlbmNlOiBCRUZPUkUiLCBzdWJ0aXRsZSA9ICJOb3VucyAmIEFkamVjdGl2ZSIpCiAgCn0KCkNPX09DX25vdW5fYWRqX3NhbWVfc2VudC5hZnRlciA8LSBmdW5jdGlvbihkZjIpewogIAogIGxpYnJhcnkoaWdyYXBoKQogIGxpYnJhcnkoZ2dyYXBoKQogIGxpYnJhcnkoZ2dwbG90MikKICAKICBjb29jIDwtIGNvb2NjdXJyZW5jZSh4ID0gc3Vic2V0KGRmMiwgdXBvcyAlaW4lIGMoIk5PVU4iLCAiQURKIikpLCAKICAgICAgICAgICAgICAgICAgICAgICB0ZXJtID0gImxlbW1hIiwgCiAgICAgICAgICAgICAgICAgICAgICAgZ3JvdXAgPSBjKCJkb2NfaWQiLCAicGFyYWdyYXBoX2lkIiwgInNlbnRlbmNlX2lkIikpCgogIHdvcmRuZXR3b3JrIDwtIGhlYWQoY29vYywgNjApCiAgd29yZG5ldHdvcmsgPC0gZ3JhcGhfZnJvbV9kYXRhX2ZyYW1lKHdvcmRuZXR3b3JrKQogIAogIGdncmFwaCh3b3JkbmV0d29yaywgbGF5b3V0ID0gImZyIikgKwogICAgZ2VvbV9lZGdlX2xpbmsoYWVzKHdpZHRoID0gY29vYywgZWRnZV9hbHBoYSA9IGNvb2MpLCBlZGdlX2NvbG91ciA9ICJsaWdodGdyZWVuIikgKwogICAgZ2VvbV9ub2RlX3RleHQoYWVzKGxhYmVsID0gbmFtZSksIGNvbCA9ICJkYXJrYmx1ZSIsIHNpemUgPSA0KSArCiAgICB0aGVtZV9ncmFwaChiYXNlX2ZhbWlseSA9ICJBcmlhbCBOYXJyb3ciKSArCiAgICB0aGVtZShsZWdlbmQucG9zaXRpb24gPSAibm9uZSIpICsKICAgIGxhYnModGl0bGUgPSAiQ29vY2N1cnJlbmNlcyB3aXRoaW4gc2VudGVuY2U6IEFGVEVSIiwgc3VidGl0bGUgPSAiTm91bnMgJiBBZGplY3RpdmUiKQogIAp9CgoKQ09fT0Nfbm91bl9hZGpfc2FtZV9zZW50LmJlZm9yZSh4X29kZC5iZWZvcmUpCkNPX09DX25vdW5fYWRqX3NhbWVfc2VudC5hZnRlcih4X2V2ZW4uYWZ0ZXIpCgoKIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCgpDT19PQ19ub3VuX2Fkal9mb2xsb3dpbmcuYmVmb3JlIDwtIGZ1bmN0aW9uKGRmKXsKICBjb29jIDwtIGNvb2NjdXJyZW5jZShkZiRsZW1tYSwgcmVsZXZhbnQgPSBkZiR1cG9zICVpbiUgYygiTk9VTiIsICJBREoiKSwgc2tpcGdyYW0gPSAxKQogIGhlYWQoY29vYykKICAKICB3b3JkbmV0d29yayA8LSBoZWFkKGNvb2MsIDYwKQogIHdvcmRuZXR3b3JrIDwtIGdyYXBoX2Zyb21fZGF0YV9mcmFtZSh3b3JkbmV0d29yaykKICBnZ3JhcGgod29yZG5ldHdvcmssIGxheW91dCA9ICJmciIpICsKICAgIGdlb21fZWRnZV9saW5rKGFlcyh3aWR0aCA9IGNvb2MsIGVkZ2VfYWxwaGEgPSBjb29jKSwgZWRnZV9jb2xvdXIgPSAibGlnaHRncmVlbiIpICsKICAgIGdlb21fbm9kZV90ZXh0KGFlcyhsYWJlbCA9IG5hbWUpLCBjb2wgPSAiZGFya2dyZWVuIiwgc2l6ZSA9IDQpICsKICAgIHRoZW1lX2dyYXBoKGJhc2VfZmFtaWx5ID0gIkFyaWFsIE5hcnJvdyIpICsKICAgIGxhYnModGl0bGUgPSAiV29yZHMgZm9sbG93aW5nIG9uZSBhbm90aGVyOiBCRUZPUkUiLCBzdWJ0aXRsZSA9ICJOb3VucyAmIEFkamVjdGl2ZSIpCn0KCkNPX09DX25vdW5fYWRqX2ZvbGxvd2luZy5hZnRlciA8LSBmdW5jdGlvbihkZil7CiAgY29vYyA8LSBjb29jY3VycmVuY2UoZGYkbGVtbWEsIHJlbGV2YW50ID0gZGYkdXBvcyAlaW4lIGMoIk5PVU4iLCAiQURKIiksIHNraXBncmFtID0gMSkKICBoZWFkKGNvb2MpCiAgCiAgd29yZG5ldHdvcmsgPC0gaGVhZChjb29jLCA2MCkKICB3b3JkbmV0d29yayA8LSBncmFwaF9mcm9tX2RhdGFfZnJhbWUod29yZG5ldHdvcmspCiAgZ2dyYXBoKHdvcmRuZXR3b3JrLCBsYXlvdXQgPSAiZnIiKSArCiAgICBnZW9tX2VkZ2VfbGluayhhZXMod2lkdGggPSBjb29jLCBlZGdlX2FscGhhID0gY29vYyksIGVkZ2VfY29sb3VyID0gInNreWJsdWUiKSArCiAgICBnZW9tX25vZGVfdGV4dChhZXMobGFiZWwgPSBuYW1lKSwgY29sID0gImRhcmtibHVlIiwgc2l6ZSA9IDQpICsKICAgIHRoZW1lX2dyYXBoKGJhc2VfZmFtaWx5ID0gIkFyaWFsIE5hcnJvdyIpICsKICAgIGxhYnModGl0bGUgPSAiV29yZHMgZm9sbG93aW5nIG9uZSBhbm90aGVyOiBBRlRFUiIsIHN1YnRpdGxlID0gIk5vdW5zICYgQWRqZWN0aXZlIikKfQoKCkNPX09DX25vdW5fYWRqX2ZvbGxvd2luZy5iZWZvcmUoeF9vZGQuYmVmb3JlKQpDT19PQ19ub3VuX2Fkal9mb2xsb3dpbmcuYWZ0ZXIoeF9ldmVuLmFmdGVyKQoKYGBgCgojIyBDb29jY3VyZW5jZXMgKHBhcnQgMikKYGBge3J9CgpDb3JycyA8LSBmdW5jdGlvbihkZil7CiAgZGYkaWQgPC0gdW5pcXVlX2lkZW50aWZpZXIoZGYsIGZpZWxkcyA9IGMoInNlbnRlbmNlX2lkIiwgImRvY19pZCIpKQogIGR0bSA8LSBzdWJzZXQoZGYsIHVwb3MgJWluJSBjKCJOT1VOIiwgIkFESiIpKQogIGR0bSA8LSBkb2N1bWVudF90ZXJtX2ZyZXF1ZW5jaWVzKGR0bSwgZG9jdW1lbnQgPSAiaWQiLCB0ZXJtID0gImxlbW1hIikKICBkdG0gPC0gZG9jdW1lbnRfdGVybV9tYXRyaXgoZHRtKQogIGR0bSA8LSBkdG1fcmVtb3ZlX2xvd2ZyZXEoZHRtLCBtaW5mcmVxID0gNSkKICB0ZXJtY29ycmVsYXRpb25zIDwtIGR0bV9jb3IoZHRtKQogIHkgPC0gYXNfY29vY2N1cnJlbmNlKHRlcm1jb3JyZWxhdGlvbnMpCiAgeSA8LSBzdWJzZXQoeSwgdGVybTEgPCB0ZXJtMiAmIGFicyhjb29jKSA+IDAuMikKICB5IDwtIHlbb3JkZXIoYWJzKHkkY29vYyksIGRlY3JlYXNpbmcgPSBUUlVFKSwgXQogIHByaW50KHlbMToyNSxdKQp9CgpgYGAKYGBge3IgY29ycnN9CgpDb3Jycyh4X29kZC5iZWZvcmUpCgpDb3Jycyh4X2V2ZW4uYWZ0ZXIpCgpgYGAKCmBgYHtyIGZpbmFsfQoKcm0obGlzdD1scygpKQoKYGBgCgoKCgoKW2hvbWVdKGh0dHBzOi8vYnJlZ3JlZW4uZ2l0aHViLmlvLykK